#include <iostream>
#include <fstream>
#include <cstdlib>
#include <pthread.h>
#include "sock.h"
#include "sockdist.h"
#include <cstdint>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <cstdint>
#include <list>
#include <sys/stat.h>
#include <map>
#include <limits>
#include <signal.h>
#include <arpa/inet.h>
#include "common_values.h"

using namespace std;

/* Fichier client */

Sock * BRLoc;
Sock * BRudLoc;
SockDist * BRPub; // serveur distant
SockDist * BRud;  // boite emission/reception
struct sockaddr_in * adrBRPub;
struct sockaddr_in * adrBRud;
int descDist;
int descUD;

// Macro définie dans common_values.h
bool verbose = false;

char * prot_mess; // Le serv attend char[256] 
char * adresse_serv;
list<pair<string,uint32_t>> toDownload, toUpload;
pthread_mutex_t lock_toDownload = PTHREAD_MUTEX_INITIALIZER, lock_toUpload = PTHREAD_MUTEX_INITIALIZER;

typedef struct parameters_struct
{
  int desc; //Descripteur du circuit virtuel
  pthread_mutex_t * lock;
  pthread_cond_t * cond;
  list<pair<string,uint32_t>> * toProcess;
  bool go_on;
} parameters_t;

int getFileList();
int getFile();
int sendFile();
int quit();
int deleteFile();

void sighandler(int signum)
{
  cout<<"\n# Interruption signalée...deconnexion du serveur..."<<endl;

  memset(prot_mess,0,ACTION_SIZE);
  strcpy(prot_mess, QUIT);
  if (send(descDist,prot_mess,ACTION_SIZE,0)==-1)
    {
      cerr << "Erreur lors du protocole de fin de communication..."<<endl;
      perror("Détails");
    } 
  quit();
  cout << "# Fin de programme ...\n";
  exit(1);
}

void * threadEmission(void * param)
{
  parameters_t * p = (parameters_t *) param;
  string path;
  ifstream file;
  unsigned int size_of_buffer = 4096; //4Ko
  char * buffer = new char[size_of_buffer];
  ssize_t read_chars;
  uint32_t total;
  
  while (p->go_on)
    {
      pthread_mutex_lock(p->lock);
      while ((p->toProcess->size()==0))
	{
	  pthread_cond_wait(p->cond,p->lock);
	}

      path = p->toProcess->front().first; //On récupère le nom de fichier
      file.open(path.c_str(),ios::in | ios::binary ); //Ouvrir le fichier en lecture
      pthread_mutex_unlock(p->lock);
      total=0;
      if (file.is_open())
	{
	  do {
	    file.read(buffer,size_of_buffer);
	    read_chars = file.gcount();
	    send(p->desc,buffer,read_chars,0);
	    total+=read_chars;
	  } while ((read_chars==size_of_buffer) && (total<(p->toProcess->front().second)));
	  file.close();
	}
      else
	{
	  _V cerr<<"Erreur ! Impossible de créer le fichier "<<path<<endl;
	}
      p->toProcess->pop_front(); //Et on l'enlève de la liste
    }
  delete [] buffer;
  pthread_exit(NULL);
}

void * threadReception(void * param)
{
  parameters_t * p = (parameters_t *) param;
  string path;
  ofstream file;
  unsigned int size_of_buffer = 4096; //4Ko
  char * buffer = new char[size_of_buffer];
  ssize_t n;
  uint32_t total;
  
  while (p->go_on)
    {
      pthread_mutex_lock(p->lock);
      while ((p->toProcess->size()==0))
	{
	  pthread_cond_wait(p->cond,p->lock);
	}

      path = p->toProcess->front().first; //On récupère le nom de fichier
      file.open(path.c_str(),ios::out | ios::trunc | ios::binary); //Ouvrir le fichier
      pthread_mutex_unlock(p->lock);
      //Le fichier vide a été créé et le mutex bloqué
      total=0;
      if (file.is_open())
	{
	  while ((total<(p->toProcess->front().second)) &&((n=recv(p->desc,buffer,size_of_buffer,0))>0))
	    {
	      file.write(buffer,n);
	      total+=n;
	    }
	  file.close();
	}
      else
	{
	  _V cerr<<"Erreur ! Impossible de créer le fichier "<<path<<endl;
	}
      p->toProcess->pop_front(); //Et on l'enlève de la liste
    }
  delete [] buffer;
  pthread_exit(NULL);
}


// Le main est le main thread, i.e le thread secrétaire  
int main(int argc, char *argv[], char *envp[])
{
  if(argc > 3 || argc <= 1)
    {
      cerr << "Format : "<<argv[0]<<" [-v]"<<" <ADRESSE SERVEUR>"
	   <<"\nL'option -v permet un affichage des logs de connection, de threads etc..."
	   <<"\nL'affichage des erreurs reste active dans tous les cas."
	   <<"\nNombre d'arguments invalide : "<<argc<<"."<<endl;
      return 1;
    }
  if(argc == 2) // pas d'option
    {
      adresse_serv = argv[1];
    }
  if(argc == 3) // Verbose en param
    {
      if(argv[1][1]=='v') // on prend le 'v' de '-v'
	{
	  cout <<"# Verbose mode !"<<endl;
	  verbose = true;
	  adresse_serv=argv[2];
	}
      else
	{
	  cerr << "Format : "<<argv[0]<<" [-v]"<<" <ADRESSE SERVEUR>"
	       <<"\nL'option -v permet un affichage des logs de connection, de threads etc..."
	       <<"\nL'affichage des erreurs reste active dans tous les cas."
	       <<"\nOption invalide : "<<"'"<<argv[1]<<"'"<<endl;
	  return 1;
	}
    }
  _V cout<<"# Création de la boîte réseau locale"<<endl;
  BRLoc = new Sock(SOCK_STREAM,0,0);
  if (BRLoc->good())
    {
      descDist=BRLoc->getsDesc();
      _V cout<<"# Boîte réseau créée"<<endl;
    }
  else
    {
      cerr<<"# Erreur lors de la création de la boîte réseau"<<endl;
      exit(1);
    }

  /*Désignation de la BR distante*/
  /*Port du serv en dur ici */
  /*argv[] = adresse */

  BRPub = new SockDist(adresse_serv,SERV_PORT);
  adrBRPub = BRPub->getAdrDist();
  //cout<<adrBRPub->sin_addr.s_addr<<endl; //Adresse IP du serveur
  socklen_t lenBRPub = sizeof(struct sockaddr_in);

  _V cout<<"# Connexion au serveur..."<<endl;
  if (int Rconnect=connect(descDist,(struct sockaddr *)adrBRPub,lenBRPub)<0)
    {      cerr<<"Erreur lors de la demande de connexion ("<<Rconnect<<")"
	       <<"\nPeut-être y a-t-il une erreur dans l'adresse du serveur distant ?"
	       <<"\nOu peut-être que le serveur distant est inaccessible ?"<<endl;
      perror("Détails");
      exit(2);
    }
  _V cout<<"# Connexion réussie"<<endl;
  
  _V cout<<"# Création de la boite réseau I/O ..."<<endl;
  BRudLoc = new Sock(SOCK_STREAM,0);
  if (BRudLoc->good())
    {
      descUD=BRudLoc->getsDesc();
      _V cout<<"# Boîte réseau I/O créée."<<endl;
    }
  else
    {
      cerr<<"# Erreur lors de la création de la boîte réseau I/O"<<endl;
      exit(1);
    }

  // Reception du port alloué pour les threads emission/reception
  unsigned short port;
  if (recv(descDist,&port,sizeof(unsigned short),0)==-1)
    {
      shutdown(descDist, SHUT_RDWR);
      close(descDist);
      shutdown(descUD, SHUT_RDWR);
      close(descUD);
      cerr << "Erreur de reception de port attribué à la BR I/O.\n";cout.flush();
      perror("Détails");
      exit(2);
    }
  _V cout<<"port numéro "<<port<<endl;
  BRud = new SockDist(adresse_serv, port);
  adrBRud=BRud->getAdrDist();
  socklen_t lenBRud = sizeof(struct sockaddr_in);
  _V cout <<"# Connexion au serveur de transmission de fichiers..."<<endl;
  if (connect(descUD,(struct sockaddr *)adrBRud,lenBRud))
    {
      cerr<<"Erreur lors de la demande de connexion"<<endl;
      perror("Détails");
      shutdown(descDist, SHUT_RDWR);
      close(descDist);
      shutdown(descUD, SHUT_RDWR);
      close(descUD);
      exit(2);
    }
  
  _V cout <<"# Connexion réussie ..."<<endl;
  _V cout <<"# Création des threads de transmission de fichiers..."<<endl;

  pthread_t tEmission, tReception;
  pthread_cond_t cond_reception= PTHREAD_COND_INITIALIZER;
  pthread_cond_t cond_emission = PTHREAD_COND_INITIALIZER;
  
  parameters_t parameters_reception;
  parameters_reception.desc = descUD; parameters_reception.cond = &cond_reception; parameters_reception.go_on = true;
  parameters_reception.lock=&lock_toDownload; parameters_reception.toProcess=&toDownload;
  parameters_t parameters_emission;
  parameters_emission.desc = descUD; parameters_emission.cond = &cond_emission; parameters_emission.go_on = true;
  parameters_emission.lock=&lock_toUpload; parameters_emission.toProcess=&toUpload;
  
  if (pthread_create(&tReception,NULL,threadReception,(void *)&parameters_reception) !=0)
    {
      cerr <<"Erreur lors de la création du thread de réception"<<endl;
      perror("");
      exit(2);
    }

  if (pthread_create(&tEmission,NULL,threadEmission,(void *)&parameters_emission) !=0)
    {
      cerr <<"Erreur lors de la création du thread d'émission"<<endl;
      perror("");
      exit(2);
    }

  _V cout <<"# Threads créés..."<<endl;
  signal(SIGINT, sighandler);
  /************/
  prot_mess = new char[ACTION_SIZE];
  cout<<"# Connecté !"<<endl;
  while (true)
    {
      cout <<"\n\n\t? Que voulez-vous faire ?\n\n"
	   << "1:(list)     : Lister les fichiers sur le serveur\n"
	   << "2:(download) : Download un fichier\n"
	   << "3:(upload)   : Upload un fichier\n"
	   << "4:(rename)   : Renommer un fichier\n"
	   << "5:(delete)   : Supprimer un fichier\n"
	   << "6:(quit)     : Quitter\n"<< endl;

      char u_choix[256];
      cin.getline(u_choix,256);
      int choix = atoi(u_choix);
      switch(choix)
	{
	case 1:
	  if (getFileList())
	    {cerr << "Erreur lors de la réception de la liste des fichiers" << endl;}
	  break;
	case 2:
	  if (getFile())
	    {cerr <<"Erreur lors de la réception d'un fichier"<<endl;}
	  else
	    {pthread_cond_signal(&cond_reception);}
	  break;
	case 3:
	  if (sendFile())
	    {cerr<<"Erreur de l'envoi d'un fichier"<<endl;}
	  else
	    {pthread_cond_signal(&cond_emission);}
	  break;
	case 5:
	  if(deleteFile())
	    cerr <<"Erreur de suppression..."<<endl;
	  break;
	case 6:
	  memset(prot_mess,0,ACTION_SIZE);
	  strcpy(prot_mess, QUIT);
	  if (send(descDist,prot_mess,ACTION_SIZE,0)==-1)
	    {
	      cerr << "Erreur lors du protocole de fin de communication..."<<endl;
	      perror("Détails");
	    } 
	  quit();
	  cout << "# Fin de programme ...\n";
	  return EXIT_SUCCESS;
	default:
	  // do while
	  break;
	}
    }
  return EXIT_SUCCESS;
}

/*
 * Format du protocole :
 * envoi de la commande .../list
 * reception taille de la liste
 * reception de la liste (format : file1/file2/..../filen)
 */
int getFileList()
{
  cout <<"# Listing des fichiers..."<<endl;
  uint32_t nb_files_expected=0;
  // fonction demandant la liste des fichiers sur le serveur distant
  memset(prot_mess,0,ACTION_SIZE);
  strcpy(prot_mess, LIST);
  // Envoi de la requete list
       
  if (send(descDist,prot_mess,ACTION_SIZE,0)==-1)
    {
      cerr << "erreur d'envoi " << cerr.flush();
      perror("Détails");
      return -1;
    }
  else
    {
      uint32_t file_list_size = (uint32_t)-1; // taille du char
      // On attend la taille de la liste de fichiers

     
      if(recv(descDist,&nb_files_expected,sizeof(nb_files_expected),0)==-1)
	{
	  return -1;
	}

      if(recv(descDist,&file_list_size,sizeof(file_list_size),0)==-1)
	{
	  return -1;
	}
      else
	{
	  // si on recoit une taille erreur
	  if(file_list_size==(uint32_t)-1)
	    {
	      cerr << "Erreur serveur : -1 recu" << cerr.flush();
	      return -1;
	    }
	  _V cout << "# Collecte des données serveur... "<<endl;
	  cout.flush(); 

	  // On peut désormais créer la liste de noms de fichiers
	  char file_list[file_list_size];

	  // exemple de reception attendue
	  //strcpy(file_list,"adc.txt//sdfsdf.xml//sdfoisd.cpp//ldskjfds.txt//osdifds.go");
 
	  if(recv(descDist,&file_list,sizeof(file_list),0)==-1)
	    {
	      perror("Détails : ");
	      return -1;
	    }
	  else
	    {
	      // Pour l'affichage, tokenization de la liste de noms de fichiers formatée par le serveur
	      char * token = NULL;
	      char * saveptr;
	      token = strtok_r(file_list,"//",&saveptr); // strtok_r est thread_safe
	      _V cout <<"# Affichage de la liste des fichiers actuellement sur le serveur...\n";
	      // while(token != NULL)
	      for(uint32_t i=0;i<nb_files_expected;i++)
		{
		  cout << "--> "<<token << endl;
		  token = strtok_r(NULL,"//",&saveptr);
		}
	    }
	}      
    }
  cout << "# "<<nb_files_expected<<" fichiers sur le serveur.\n\n";
  cout.flush();
  return 0;
}

int sendFile()
{
  string f_to_send;
  cout<<"Chemin du fichier à envoyer : "; cout.flush();
  cin.clear();
  getline(std::cin,f_to_send);
  
  uint16_t path_len, net_path_len;
  path_len = strlen(f_to_send.c_str());
  net_path_len = htons(path_len);

  uint32_t f_size=-1, f_net_size;
  struct stat stat_buf; //Pour récupérer la taille du fichier
  int rc = stat(f_to_send.c_str(), &stat_buf);
  if (rc) //Si erreur lors de la récupération des informations
  {
    f_size =(uint32_t)-1;
    //cerr<<"Erreur lors du stat du fichier"<<endl;
    cerr<<"Fichier "<<f_to_send<<" introuvable"<<endl;
    return -1;
  }
  else
  {
    if (not S_ISREG(stat_buf.st_mode))
    {
      f_size = -1;
      cerr<<f_to_send<<" n'est pas un fichier régulier !"<<endl;
      return -1;
    }
    else
    {f_size = stat_buf.st_size;}
  }
  f_net_size = htonl(f_size);

  memset(prot_mess,0,ACTION_SIZE);
  strcpy(prot_mess,UPLOAD);
  if (-1==send(descDist,prot_mess,ACTION_SIZE,0))
  {
    cerr<< "Erreur lors de l'envoi de la commande"<<endl;
    perror("");
    return -1;
  }
  _V cout<<"# Demande d'upload au serveur..."<<endl;
  
  if (-1==send(descDist,&net_path_len,sizeof(net_path_len),0)) //Envoi de la taille du path
    {
      cerr<<"Erreur de transmission de taille de path"<<endl;
      perror("");
      return -1;
    }
  if (-1==send(descDist,f_to_send.c_str(),path_len,0)) //Envoi du path
    {
      cerr<<"Erreur lors de l'envoi du path"<<endl;
      perror("");
      return -1;
      }
  if ((-1==send(descDist,&f_net_size,sizeof(f_net_size),0)) || (f_size==(uint32_t)-1)) //Envoi de la taille du fichier
  {
    cout<<"Erreur lors de l'envoi de la taille du fichier"<<endl;
    return -1; //Si le fichier n'a pas pu être analysé, ou s'il y a eu erreur d'envoi
  }
  if ((-1==recv(descDist,&f_size,sizeof(f_size),0)))
  {
    cout<<"Erreur lors de la réception de l'acknowledge..."<<endl;
    return -1;
  }
  f_size = ntohl(f_size);
  if (f_size == (uint32_t)-1)
  {
    cout<<"Le fichier existe déjà sur le serveur, impossible de l'uploader en état"<<endl;
    return -1;
  }
  pthread_mutex_lock(&lock_toUpload);
  toDownload.emplace(toUpload.end(),f_to_send,f_size); //Ajout du fichier à la liste
  pthread_mutex_unlock(&lock_toUpload);
  cout<<"Envoi du fichier par le circuit virtuel dédié..."<<endl;
  return 0;
}

int getFile()
{
  memset(prot_mess,0,ACTION_SIZE);
  strcpy(prot_mess,DOWNLOAD);
  if (send(descDist,prot_mess,ACTION_SIZE,0)==-1)
    {
      cerr << "Erreur lors de l'envoi de la commande" << endl;
      perror("");
      return -1;
    }
  string f_r;
  cout << "Nom du fichier à télécharger : "; cout.flush();
  cin.clear();
  getline(std::cin,f_r);

  uint16_t path_len = strlen(f_r.c_str())+1;
  uint16_t net_path_len = htons(path_len);
  
  _V cout << "# Demande de téléchargement au serveur..." <<f_r<<endl;
  // envoi de la taille du path
  if (send(descDist,&net_path_len,sizeof(net_path_len),0)==-1)
    {
      cerr << "Erreur de transmission de taille de path"<<endl;
      perror("");
      return -1;
    }
  // envoi du path
  if (send(descDist,f_r.c_str(),path_len,0)==-1)
    {
      cerr << "Erreur de transmission de path"<<endl;
      return -1;
    }

  // reception de la taille du fichier
  // -1 si fichier n'existe pas
  uint32_t f_size =-1;
  if (recv(descDist,&f_size,sizeof(f_size),0)==-1)
    {
      cerr << "Erreur de transmission de taille de fichier"<<endl;
      return -1;
    }
  f_size = ntohl(f_size);
  if(f_size == (uint32_t)-1)
    {
      cerr <<"Soit le fichier "<<f_r<<" n'existe pas sur le serveur, soit il est en cours de modification..."<<endl;
      return -1;
    }

		
  _V cout <<"# Taille du fichier : " <<f_size<<" octets"<<endl;
  _V cout <<"# Ajout de la demande de fichier à la liste des fichiers..."<<endl;
  // ajout de ce path à la liste des fichiers
  pthread_mutex_lock(&lock_toDownload);
  toDownload.emplace(toDownload.end(),f_r,f_size);
  pthread_mutex_unlock(&lock_toDownload);  
  return 0;
}

int deleteFile()
{
  memset(prot_mess,0,ACTION_SIZE);
  strcpy(prot_mess, DELETE);
  if (send(descDist,prot_mess,ACTION_SIZE,0)==-1)
    {
      cerr << "Erreur lors du protocole de suppression..."<<endl;
      perror("Détails");
    }
 
  string toDelete;
  cout << "Nom du fichier à supprimer : "; cout.flush();
  getline(std::cin,toDelete);
  
  uint16_t path_len = strlen(toDelete.c_str());
  uint16_t net_path_len = htons(path_len);
  
  _V cout <<"# Demande de suppression au serveur..."<<toDelete<<endl;
  
  // envoi de la taille du path
  if (send(descDist,&net_path_len,sizeof(net_path_len),0)==-1)
    {
      cerr << "Erreur de transmission de taille de path"<<endl;
      perror("");
      return -1;
    }
  // envoi du path
  if (send(descDist,toDelete.c_str(),path_len,0)==-1)
    {
      cerr << "Erreur de transmission de path"<<endl;
      return -1;
    }

  int code;
  if (recv(descDist,&code,sizeof(code),0)==-1)
    {
      cerr << "Erreur de transmission de taille de fichier"<<endl;
      return -1;
    }
  code = ntohs(code);
  switch(code)
    {
    case 0:
      cout << "# Ce fichier n'existe pas sur le serveur..."<<endl;
      return -1;
      break;
    case -1:
      cout << "# Ce fichier existe mais est actuellement en cours de modification sur le serveur..."<<endl;
      return -1;
      break;
    case -2:
      cout << "# Problème interne au serveur pour la suppression..."<<endl;
      return -1;
      break;
    case 1:
      cout << "# Fichier supprimé ..."<<endl;
      return 0;
      break;
    default:
      return -1;
    }     
  return 0;
}

int quit()
{
  memset(prot_mess,0,ACTION_SIZE);
  strcpy(prot_mess, QUIT);
  if (send(descDist,prot_mess,ACTION_SIZE,0)==-1)
    {
      cerr << "Erreur lors du protocole de fin de communication..."<<endl;
      perror("Détails");
    }
  shutdown(descDist,SHUT_RDWR);
  close(descDist);
  delete BRPub;
  shutdown(descUD,SHUT_RDWR);
  close(descUD);
  delete BRLoc;
  delete prot_mess;

  return 0;
}
